#if !defined(ximera_exception_hpp)
#define ximera_exception_hpp

// -----------------------------------------------------------

#include <ximera/types.hpp>
#include <ximera/logger.hpp>
#include <ximera/stacktrace.hpp>
#include <stdexcept>

namespace ximera
{
    #define XIMERA_THROW(exception, args) throw exception(XIMERA_FILE, XIMERA_FUNCTION, XIMERA_LINE, #exception, args)
    #define XIMERA_THROW_NOARGS(exception) throw exception(XIMERA_FILE, XIMERA_FUNCTION, XIMERA_LINE)

    // -----------------------------------------------------------

    #define XIMERA_DECLARE_EXCEPTION_MAIN(api, extype, parent, msg) \
                class api extype : public parent \
                { \
                public: \
                    extype(char const* fileName = "", char const* function = "", int line = 0, \
                         char const* type = #extype, String const& message = msg) \
                        : parent(fileName, function, line, type, message)\
                    {\
                        _trace.popFront();\
                    }\
                };

    #define XIMERA_DECLARE_EXCEPTION_SUB(extype, parent, msg) \
                XIMERA_DECLARE_EXCEPTION_MAIN(XIMERA_API, extype, parent, msg)

    #define XIMERA_DECLARE_EXCEPTION(extype, msg) \
                XIMERA_DECLARE_EXCEPTION_SUB(extype, ximera::Exception, msg)

    #define XIMERA_DECLARE_PLUGIN_EXCEPTION_SUB(extype, parent, msg) \
                XIMERA_DECLARE_EXCEPTION_MAIN(XIMERA_PLUGIN_API, extype, parent, msg)

    #define XIMERA_DECLARE_PLUGIN_EXCEPTION(extype, msg) \
                XIMERA_DECLARE_PLUGIN_EXCEPTION_SUB(extype, ximera::Exception, msg)

    // -----------------------------------------------------------

    class XIMERA_API Exception : public std::exception
    {
    public:
        Exception(std::string const& fileName = "", std::string const& function = "", int line = 0,
                  std::string const& type = "Exception", String const& message = L"Unknown Error");
        virtual ~Exception();

        String const& message() const;
        std::string const& fileName() const;
        std::string const& function() const;
        std::string const& type() const;
        int line() const;
        StackTrace const& stackTrace() const;

        virtual char const* what() const;

    protected:
        String _message;
        std::string _fileName;
        std::string _function;
        std::string _type;
        int _line;
        StackTrace _trace;
        mutable std::string _whatStr;
    };
    
    inline std::wostream& operator<<(std::wostream& os, Exception const& e)
    {
        return os << L"Exception: " << e.type().c_str() << std::endl
                  << L"  Message: " << e.message() << std::endl
                  << L"  Function: " << e.function().c_str() << std::endl
                  << L"  File: " << e.fileName().c_str() << " (" << e.line() << ")" << std::endl
                  << L"  Trace: " << std::endl << e.stackTrace();
    }

    // -----------------------------------------------------------

    XIMERA_DECLARE_EXCEPTION(InvalidParamException, L"Invalid Parameter");
    XIMERA_DECLARE_EXCEPTION(DivideByZeroException, L"Attempting to divide by zero!");
    XIMERA_DECLARE_EXCEPTION(OutOfRangeException, L"Value is out of range");
    XIMERA_DECLARE_EXCEPTION(ItemNotFoundException, L"Item was not found");
    XIMERA_DECLARE_EXCEPTION(NotImplementedException, L"Not implemented");
    XIMERA_DECLARE_EXCEPTION(NotInitializedException, L"Not initialized");
    XIMERA_DECLARE_EXCEPTION(NullPointerException, L"Null pointer");
}

// -----------------------------------------------------------

#endif
